package club.cearnach.api.security.config;

import club.cearnach.api.security.handler.MovieAuthenticationFailureHandler;
import club.cearnach.api.security.handler.MovieAuthenticationSuccessHandler;
import club.cearnach.api.security.handler.MovieLogoutSuccessHandler;
import club.cearnach.core.property.MovieSecurityProperties;
import club.cearnach.security.filter.UserAuthenticationFilter;
import club.cearnach.security.provider.MovieAuthorizeConfigProviderManager;
import club.cearnach.security.provider.impl.UserDetailsAuthenticationProvider;
import club.cearnach.security.strategy.ExpiredSessionStrategy;
import club.cearnach.security.strategy.MovieInvalidSessionStrategy;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.authentication.rememberme.JdbcTokenRepositoryImpl;
import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository;

import javax.sql.DataSource;

/**
 * @author 阮胜
 * @date 2018/5/23 14:08
 */
@Configuration
@Slf4j
public class MovieSecurityConfig extends WebSecurityConfigurerAdapter {
    private final MovieAuthenticationSuccessHandler movieAuthenticationSuccessHandler;
    private final MovieAuthenticationFailureHandler movieAuthenticationFailureHandler;
    private final MovieAuthorizeConfigProviderManager movieAuthorizeConfigProviderManager;
    private final MovieLogoutSuccessHandler movieLogoutSuccessHandler;
    private final MovieInvalidSessionStrategy movieInvalidSessionStrategy;
    private final ExpiredSessionStrategy expiredSessionStrategy;
    private final DataSource dataSource;
    private final UserDetailsAuthenticationProvider userDetailsAuthenticationProvider;
    @Autowired
    private MovieSecurityProperties movieSecurityProperties;
    @Autowired
    private EmailVerificationCodeSecurityConfig emailVerificationCodeSecurityConfig;
    @Autowired
    @Qualifier("normalUserDetailService")
    private UserDetailsService userDetailsService;
    @Autowired
    @Qualifier("adminDetailService")
    private UserDetailsService adminDetailService;

    public MovieSecurityConfig(MovieAuthenticationSuccessHandler movieAuthenticationSuccessHandler, MovieAuthenticationFailureHandler movieAuthenticationFailureHandler, MovieAuthorizeConfigProviderManager movieAuthorizeConfigProviderManager, MovieLogoutSuccessHandler movieLogoutSuccessHandler, MovieInvalidSessionStrategy movieInvalidSessionStrategy, ExpiredSessionStrategy expiredSessionStrategy, DataSource dataSource, UserDetailsAuthenticationProvider userDetailsAuthenticationProvider) {
        this.movieAuthenticationSuccessHandler = movieAuthenticationSuccessHandler;
        this.movieAuthenticationFailureHandler = movieAuthenticationFailureHandler;
        this.movieAuthorizeConfigProviderManager = movieAuthorizeConfigProviderManager;
        this.movieLogoutSuccessHandler = movieLogoutSuccessHandler;
        this.movieInvalidSessionStrategy = movieInvalidSessionStrategy;
        this.expiredSessionStrategy = expiredSessionStrategy;
        this.dataSource = dataSource;
        this.userDetailsAuthenticationProvider = userDetailsAuthenticationProvider;
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) {
        //增加自定义的UserDetailService
        userDetailsAuthenticationProvider.setUserDetailsService(userDetailsService);
        //设置一个Provider
        auth.authenticationProvider(userDetailsAuthenticationProvider);

    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        //手动实现的权限验证管理器
        movieAuthorizeConfigProviderManager.configure(http.authorizeRequests());

        //添加前台登录验证过滤器与userDetailService,不同的登录处理URL需要在Filter里面指定
        UserAuthenticationFilter userAuthenticationFilter = new UserAuthenticationFilter();
        //每个Filter必须指定一个authenticationManager
        userAuthenticationFilter.setAuthenticationManager(authenticationManager());
        //设置登录成功处理事件
        userAuthenticationFilter.setAuthenticationSuccessHandler(movieAuthenticationSuccessHandler);
        //设置登录失败处理事件
        userAuthenticationFilter.setAuthenticationFailureHandler(movieAuthenticationFailureHandler);
        http.addFilterBefore(userAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
        //http.apply(authenticationCodeSecurityConfig);
        http.apply(emailVerificationCodeSecurityConfig);
        http.formLogin()
                //登录操作
                .loginPage(movieSecurityProperties.getAdminLoginPageUrl())
                .loginProcessingUrl(movieSecurityProperties.getAdminLoginCheckUrl())
                .successHandler(movieAuthenticationSuccessHandler)
                .failureHandler(movieAuthenticationFailureHandler).permitAll()
                //userDetailsService不能在rememberMe之后,否则无效
                .and().userDetailsService(adminDetailService)
                //登出操作
                .logout().logoutUrl(movieSecurityProperties.getAdminLogoutUrl())
                .logoutSuccessHandler(movieLogoutSuccessHandler).permitAll()
                //记住我功能
                .and().rememberMe().tokenRepository(persistentTokenRepository())
                //token "记住我"功能的token过期时间(秒)
                .tokenValiditySeconds(60 * 60 * 24 * 7)
                //长时间未操作自动消除session后处理.可以在yml文件中配置超时时间
                .and().sessionManagement()
                //session失效时跳转的url,这个配置和invalidSessionStrategy是互斥的,2个同时配置只能生效一个
                //.invalidSessionUrl(adminLoginInvalidUrl)
                .invalidSessionStrategy(movieInvalidSessionStrategy)
                //session 无效策略 和 invalidSessionUrl 互斥
                .maximumSessions(1)
                //session 并发策略
                //.expiredUrl(adminLoginExpiredUrl)
                .expiredSessionStrategy(expiredSessionStrategy)
                //如果账户已经登录就会阻止其他地方登录
                //.maxSessionsPreventsLogin(true)
                .and()
                .and().authorizeRequests().anyRequest().authenticated()
                .and().csrf().disable();
    }

    @Bean
    public PersistentTokenRepository persistentTokenRepository() {
        JdbcTokenRepositoryImpl jdbcTokenRepository = new JdbcTokenRepositoryImpl();
        jdbcTokenRepository.setDataSource(dataSource);
        return jdbcTokenRepository;
    }
}
